/*
 * capaFuse.c
 *
 *  Created on: 20/10/2011
 *      Author: utn_so
 */

#define FUSE_USE_VERSION 26

#include <fuse.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "cache.h"
#include "utils.h"
#include "capaFuse.h"
#include <semaphore.h>
#include <time.h>

extern tipoConfigPFS configPFS;
extern Boot_Sector BootSector;
extern Fat FAT;
extern sem_t fatSemaforo;
extern sem_t cacheSemaforo;
extern t_log *archLog;

///////////////////////////////////////////Fuse////////////////////////////////////////////////

char* directorioContenido(const char* path){
	int i, contador=0, offset;
	char *pathAux;
	pathAux=path;
	for(i=0;*pathAux!='\0';i++){
		if(*pathAux=='/')
			contador=0;
		else
			contador++;
		pathAux++;
	}
	offset=i-contador-1;              //el -1 es para que no copie el ultimo /.
	char *nuevoPath;
	if(offset==0){
		nuevoPath="/";
	}
	else{
		nuevoPath=malloc(offset);
		strncpy(nuevoPath, path, offset);
		*(nuevoPath+offset)='\0';
	}
	return nuevoPath;
}


char* nombreDestino(const char* path){
	int i, contador=0, offset;
	char *pathAux=path;
	for(i=0;*pathAux!='\0';i++){
		if(*pathAux=='/')
			contador=0;
		else
			contador++;
		pathAux++;
	}
	offset=i-contador;
	char *nombreDestino=path+offset;
	return nombreDestino;
}

int hello_open(const char *path, struct fuse_file_info *fi) {
	infoDir info;
	if (*path != '/')
		return -ENOENT;

	info = buscar(path);
	if (info.resultadoBusqueda == noEncontrado)
		return -ENOENT;

	agregarCache(path,info);
	/*------------------------LOG------------------------*/
	/*time_t tiempo = time(0);
	struct tm *tlocal = localtime(&tiempo);
	char hora[9];
	strftime(hora, 9, "%H:%M:%S", tlocal);
	log_debug(archLog, "FileSystem", "Abierto el archivo: %s\n", path);
	int comparar(void* dato) {
		return !strcmp(((fileCache*) dato)->path, path);
	}
	fileCache* entrada;
	entrada = (fileCache*) collection_list_find(fileOpenTable, comparar);
	if (entrada == NULL)
		return -EAGAIN;
	uint32_t posicionFAT = entrada->fstClus;
	if (!fat32_SeEncuentraEnTabla(posicionFAT))
		fat32_LeerFatRegion(posicionFAT);
	int contador=0;
	while (!fat32_esFinDeCadena(FAT.tabla[posicionFAT - FAT.offset]) && contador<10) {
		log_debug(archLog, "FileSystem", "Cluster %u\n", posicionFAT);

		posicionFAT = fat32_SiguienteCadenaFat(posicionFAT);
		contador++;
	}
	log_debug(archLog, "FileSystem", "Cluster %u\n", posicionFAT);*/
	/*------------------------LOG------------------------*/
	return 0;
}

unsigned char lfn_checksum(const unsigned char *pFcbName) {
	int i;
	unsigned char sum = 0;

	for (i = 11; i; i--)
		sum = ((sum & 1) << 7) + (sum >> 1) + *pFcbName++;
	return sum;
}

int hello_mkdir(const char *path, mode_t modo){
	infoDir info;
	if (*path != '/')
		return -EPERM;
	info = buscar(path);
	if (info.resultadoBusqueda == encontrado) //si encuentra el path es porque ya existe el directorio
		return -EPERM;

	char *pathDest;
	pathDest=directorioContenido(path);
	info = buscar(pathDest);

	if(info.resultadoBusqueda == noEncontrado)
		return -EPERM;		//si no encuentra la carpeta en donde crear el dencontradoir sale
	char *nombreDir;
	nombreDir = nombreDestino(path);
	Dir* Cluster;
	//Aca busca entradas libres en el directorio
	uint32_t posLibre;
	Cluster = fat32_buscarPosLibreEnDirectorio(&posLibre, info);
	LDir LFN;
	Dir DIR;
	memset(&DIR, 0, sizeof(Dir));
	memset(&LFN, 0, sizeof(LDir));
	uint32_t nroCluster;
	t_queue* clusterLibre;
	grabarNombreUTF16(&LFN,nombreDir);
	LFN.Ord = 0x41; //todo probar con 0x40
	LFN.Attr = 0x0F;
	LFN.Type = 0x00;
	LFN.FstClusLO = 0x0000;
	LFN.Chksum = 0x64; //lfn_checksum("");
	DIR.Attr = 0x10;
	DIR.FileSize = BootSector.secPerClus * 512;
	DIR.Name[0] = 0x64; // para que no este vacia esta entrada
	sem_wait(&fatSemaforo);
	clusterLibre = fat32_ObtenerNClustersLibres(1);
	nroCluster = (uint32_t) collection_queue_pop(clusterLibre);
	FAT.tabla[nroCluster - FAT.offset] = 0x0FFFFFFF;
	FAT.bitDeUso = modificado;
	sem_post(&fatSemaforo);
	char *src = (char *) &nroCluster;
	char *dst = (char *) &(DIR.FstClusLO);
	dst[0] = src[0];
	dst[1] = src[1];
	dst = (char *) &(DIR.FstClusHI);
	dst[0] = src[2];
	dst[1] = src[3];
	char *ClusSeteado = malloc(BootSector.secPerClus * 512);
	memset(ClusSeteado, 0, BootSector.secPerClus * 512);
	fat32_mandarAEscribirCluster(ClusSeteado, nroCluster);
	free(ClusSeteado);
	memcpy(&Cluster[posLibre],&LFN,32);
	Cluster[posLibre + 1] = DIR;
	fat32_mandarAEscribirCluster((char*) Cluster, info.clusterContenido);
	free(Cluster);
	collection_queue_destroy(clusterLibre, NULL);
	fat32_EscribirFatRegion();
	return 0;
}

int hello_getattr(const char *path, struct stat *stbuf) {
	int res = 0;
	infoDir info;
	Dir* Cluster;

	if(!strcmp(path, "/.Trash"))
		return -EISDIR;
	if(!strcmp(path, "/.Trash-0"))
		return -EISDIR;
	if(!strcmp(path, "/BDMV"))
		return -EISDIR;
	if(!strcmp(path, "/.Trash-1000"))
		return -EISDIR;
	if(!strcmp(path, "/.xdg-volume-info"))
		return -ENOENT;

	if (!strcmp(path, "/")) {
		stbuf->st_mode = S_IFDIR | 0755;
		stbuf->st_nlink = 1;
		stbuf->st_size = 4096; //todo size de directorio 0?
		return res;
	}
	info = buscar(path);
	if (info.resultadoBusqueda == noEncontrado)
		return -ENOENT;

	Cluster = (Dir*) fat32_ObtenerCluster(info.clusterDirectorio);
	memset(stbuf, 0, sizeof(struct stat));

	if (Cluster[info.posicionDirectorio].Attr == 0x10) { //Directorio
		stbuf->st_mode = S_IFDIR | 0755;
		stbuf->st_nlink = 1;
		stbuf->st_size = Cluster[info.posicionDirectorio].FileSize;
	} else if (Cluster[info.posicionDirectorio].Attr == 0x20) { //Archivo
		stbuf->st_mode = S_IFREG | 0444;
		stbuf->st_nlink = 1;
		stbuf->st_size = Cluster[info.posicionDirectorio].FileSize;
	} else
		res = -ENOENT;
	free(Cluster);
	return res;
}

int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) {
	//(void) offset;
	//(void) fi;
	infoDir info;
	t_queue* colaNombres=collection_queue_create();

	info=buscar(path);
	if (info.resultadoBusqueda == noEncontrado)
		return -ENOENT;

	listarDirectorio(info.clusterContenido, colaNombres);
	filler(buf, ".", NULL, 0);
	filler(buf,"..",NULL,0);
	while (!collection_queue_isEmpty(colaNombres)){
		char*nombre=(char*)collection_queue_pop(colaNombres);
		filler(buf, nombre, NULL, 0);
	}
	return 0;
}


/////////////////*************/////////////////
int hello_read(const char *path, char *buf, size_t size, off_t offset,
		struct fuse_file_info *fi) {

	(void) fi;
	uint32_t offsetNumClus, offsetDentroClus;
	char* Cluster;
	Dir* ClusDir;
	infoDir info;
	int fin = 1, j = 0, posicionFAT, sobra, tamanio, i, fileSize;

	if (*path != '/')
		return -EPERM;

	info = buscar(path);
	if (info.resultadoBusqueda == noEncontrado)
		return -ENOENT;

	ClusDir = (Dir*) fat32_ObtenerCluster(info.clusterDirectorio);
	fileSize = ClusDir[info.posicionDirectorio].FileSize;
	free(ClusDir);

	//Nos fijamos el tamanio real a leer dentro del archivo
	if (offset > fileSize)
		return -EPERM;
	if (fileSize - offset >= size) {
		tamanio = size;
		sobra = 0;
	} else {
		tamanio = fileSize - offset;
		sobra = size - tamanio;
	}
	int cantidadLeido=tamanio;

	if (configPFS.tamCache != 0) {
		char* bufAux;
		bufAux = leerCache(path, offset, tamanio);
		if (bufAux == NULL)
			return -ENOENT;
		else {
			memcpy(buf, bufAux, tamanio);
			free(bufAux);
		}

	} else { //sin cache
		offsetNumClus = offset / (BootSector.secPerClus * 512);
		posicionFAT = info.clusterContenido;
		sem_wait(&fatSemaforo);
		for (i = 0; i < offsetNumClus; i++) {
			posicionFAT = fat32_SiguienteCadenaFat(posicionFAT);
		}
		sem_post(&fatSemaforo);
		offsetDentroClus = offset - offsetNumClus * (BootSector.secPerClus
				* 512);

		while (fin) {
			Cluster = fat32_ObtenerCluster(posicionFAT);
			if (tamanio > BootSector.secPerClus * 512 - offsetDentroClus) {
				memcpy(buf + j, Cluster + offsetDentroClus,
						BootSector.secPerClus * 512 - offsetDentroClus);
				j += BootSector.secPerClus * 512 - offsetDentroClus;
				sem_wait(&fatSemaforo);
				posicionFAT = fat32_SiguienteCadenaFat(posicionFAT);
				sem_post(&fatSemaforo);
				tamanio -= BootSector.secPerClus * 512 - offsetDentroClus;
			} else {
				memcpy(buf + j, Cluster + offsetDentroClus, tamanio); //el malloc se le hace antes de entrar al read
				j += tamanio;
				fin = 0;
			}
			offsetDentroClus = 0;
			free(Cluster);
		}
	}
	if(sobra>0){
		tamanio = fileSize - offset;
		char *ceros=malloc(sobra);
		memset(ceros, 0, sobra);
		memcpy(buf + tamanio,ceros,sobra);
		free(ceros);
	}

	return cantidadLeido;
}

/////////////////*************/////////////////

int hello_rename(const char *path, const char *nuevoPath) {
	infoDir info, infoDestino;
	if (strcmp(path, "/") == 0)
			return -ENOENT;

	info = buscar(path);
	if (info.resultadoBusqueda == noEncontrado)
		return -ENOENT;

	infoDestino = buscar(nuevoPath);
	if (infoDestino.resultadoBusqueda == encontrado)
		return -EPERM;

	char *pathDest=directorioContenido(nuevoPath);
	infoDestino = buscar(pathDest);
	if (infoDestino.resultadoBusqueda == noEncontrado)
		return -EPERM;

	Dir* Cluster;
	Cluster = (Dir*)fat32_ObtenerCluster(info.clusterDirectorio);
	char *nuevoNombre = nombreDestino(nuevoPath);
	LDir LFN;
	Dir DIR;
	DIR = Cluster[info.posicionDirectorio];
	memcpy(&LFN, &Cluster[info.posicionDirectorio-1], 32);
	grabarNombreUTF16(&LFN,nuevoNombre);
	Cluster[info.posicionDirectorio-1].Name[0]=0xE5;
	Cluster[info.posicionDirectorio].Name[0]=0xE5;
	fat32_mandarAEscribirCluster((char*)Cluster, info.clusterDirectorio);
	free(Cluster);

	Dir* ClusterDest;
	int i,encontro=1;
	uint32_t aux;

	while(encontro){
	ClusterDest = (Dir*)fat32_ObtenerCluster(infoDestino.clusterContenido);
	for(i=0;i<=128 && !entradaLibre(ClusterDest[i].Name[0]);i++);
	if(i<128)
		encontro=0;
	else{
		aux=infoDestino.clusterContenido;
		sem_wait(&fatSemaforo);
		infoDestino.clusterContenido=fat32_SiguienteCadenaFat(infoDestino.clusterContenido);
		if(fat32_esFinDeCadena(infoDestino.clusterContenido)){
			fat32_AgregarACadenaDeClusters(1, aux);
			infoDestino.clusterContenido=fat32_SiguienteCadenaFat(aux);
		}
		sem_post(&fatSemaforo);
		free(ClusterDest);
	}

	memcpy(&ClusterDest[i],&LFN,32);
	ClusterDest[i+1]=DIR;
	fat32_mandarAEscribirCluster((char*)ClusterDest, infoDestino.clusterContenido);
	free(ClusterDest);
	}
	return 0;
}

/////////////////*************/////////////////
int hello_truncate(const char *path, off_t offset){
	int32_t numClus;
	int i;
	infoDir info;
	Dir* Cluster;
	uint32_t fileSize;

	if (*path!='/')
		return -ENOENT;

	int comparar(void* dato) {
		return !strcmp(((fileCache*) dato)->path, path);
	}
	fileCache *entrada = (fileCache*) collection_list_find(fileOpenTable,
			comparar);

	numClus = offset / (BootSector.secPerClus*512);
	if((offset % (BootSector.secPerClus*512))==0)
		numClus--;
	info = buscar(path);
	if(info.resultadoBusqueda == noEncontrado)
		return -ENOENT;

	Cluster = (Dir*) fat32_ObtenerCluster(info.clusterDirectorio);
	fileSize = Cluster[info.posicionDirectorio].FileSize;
	if (offset == fileSize)
		return 0;

	if(offset<fileSize){
		uint32_t posicionFAT = info.clusterContenido;
		sem_wait(&fatSemaforo);
		for (i = 0; i < numClus; i++) {
			posicionFAT = fat32_SiguienteCadenaFat(posicionFAT);
		}
		fat32_RemoverClusters(posicionFAT);
		sem_post(&fatSemaforo);
		Cluster[info.posicionDirectorio].FileSize = offset;
		if(offset==0){
			fat32_removerPrimerCluster(Cluster, &info);
			if (entrada != NULL)
				entrada->fstClus = 0;
		}
		fat32_mandarAEscribirCluster((char*)Cluster, info.clusterDirectorio);
	}else{
		//uint32_t clustersActual = fileSize/(BootSector.secPerClus*512) + 1;
		uint32_t clustersNecesarios = fat32_cantidadDeClusterAgregar(offset, fileSize); //offset/(BootSector.secPerClus*512) + 1;
		if(info.clusterContenido == 0){
			fat32_agregarPrimerCluster(Cluster, &info);
			if (entrada != NULL)
				entrada->fstClus = info.clusterContenido;
			clustersNecesarios--;
		}
		sem_wait(&fatSemaforo);
		fat32_AgregarACadenaDeClusters(clustersNecesarios, info.clusterContenido);
		sem_post(&fatSemaforo);
		char *sobras = malloc(offset - fileSize);
		memset(sobras, 0, offset - fileSize);
		Cluster[info.posicionDirectorio].FileSize = offset;
		fat32_mandarAEscribirCluster((char*)Cluster, info.clusterDirectorio);
		hello_write(path, sobras, offset-fileSize, fileSize, NULL);
	}
	free(Cluster);
	return 0;
}

int hello_unlink(const char *path){
	infoDir info;
	if (*path!='/')
		return -ENOENT;

	info = buscar(path);
	if(info.resultadoBusqueda == noEncontrado)
		return -ENOENT;

	int comparar(void* dato) {
		return !strcmp(((fileCache*) dato)->path, path);
	}
	//archivo abierto
	/*fileCache* entrada;
	entrada = (fileCache*) collection_list_find(fileOpenTable, comparar);
	if(entrada!=NULL)
		return -ENOENT;*/
	hello_truncate(path, 0);
	fat32_eliminarEntrada(info);
	return 0;
}

int hello_rmdir(const char *path){
	infoDir info;
	info = buscar(path);
	if(info.resultadoBusqueda == noEncontrado)
		return -ENOENT;

	int dirVacio;
	sem_wait(&fatSemaforo);
	dirVacio = fat32_directorioEstaVacio(info.clusterContenido);
	sem_post(&fatSemaforo);
	if(!dirVacio)
		return -EPERM;
	hello_truncate(path, 0);
	fat32_eliminarEntrada(info);

	return 0;
}

int hello_flush(const char *path, struct fuse_file_info *fi){
	fileCache *entrada;
	int comparar(void* dato) {
		return !strcmp(((fileCache*) dato)->path, path);
	}
	sem_wait(&cacheSemaforo);
	entrada = (fileCache*)collection_list_find(fileOpenTable, comparar);
	if(entrada==NULL)
		return -ENOENT;
	flushCache(entrada);
	sem_post(&cacheSemaforo);

	return 0;
}

int hello_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi){
	infoDir info;
	if(*path!='/')
		return -ENOENT;
	size_t sizeCopiados=size;
	info = buscar(path);
	if(info.resultadoBusqueda == noEncontrado)
		return -ENOENT;

	int comparar(void* dato) {
		return !strcmp(((fileCache*) dato)->path, path);
	}
	fileCache *entrada = (fileCache*)collection_list_find(fileOpenTable, comparar);
	if(entrada==NULL)
		return -ENOENT;

	Dir *entradasDir;
	entradasDir = (Dir*)fat32_ObtenerCluster(info.clusterDirectorio);
	if ((offset + size) > entradasDir[info.posicionDirectorio].FileSize) {
		uint32_t clustersNecesarios;
		clustersNecesarios = fat32_cantidadDeClusterAgregar(offset + size,
				entradasDir[info.posicionDirectorio].FileSize);
		if (info.clusterContenido == 0) {
			fat32_agregarPrimerCluster(entradasDir, &info);
			if(entrada!=NULL)
				entrada->fstClus=info.clusterContenido;
			clustersNecesarios--;
		}
		sem_wait(&fatSemaforo);
		fat32_AgregarACadenaDeClusters(clustersNecesarios, info.clusterContenido);
		sem_post(&fatSemaforo);
		entradasDir[info.posicionDirectorio].FileSize = offset + size;
		fat32_mandarAEscribirCluster((char*) entradasDir, info.clusterDirectorio);
		free(entradasDir);
	}

	if(configPFS.tamCache!=0){
		if(escribirCache(path, buf, size, offset))
			return -ENOENT;
		return sizeCopiados;
	}
	uint32_t offsetNumClus, posicionFAT, offsetDentroClus, corrimiento=0;
	int i, fin=1;
	offsetNumClus = offset / (BootSector.secPerClus * 512);
	posicionFAT = info.clusterContenido;

	sem_wait(&fatSemaforo);
	for (i = 0; i != offsetNumClus; i++) {
		posicionFAT = fat32_SiguienteCadenaFat(posicionFAT);
	}
	sem_post(&fatSemaforo);
	char *ClusterAEscribir;

	offsetDentroClus = offset - offsetNumClus * (BootSector.secPerClus * 512);
	while (fin) {
		ClusterAEscribir = fat32_ObtenerCluster(posicionFAT);
		if (size > BootSector.secPerClus * 512 - offsetDentroClus) {
			memcpy(ClusterAEscribir + offsetDentroClus, buf + corrimiento, BootSector.secPerClus * 512 - offsetDentroClus);
			corrimiento += BootSector.secPerClus * 512 - offsetDentroClus;
			size -= BootSector.secPerClus * 512 - offsetDentroClus;
		} else {
			memcpy(ClusterAEscribir + offsetDentroClus, buf + corrimiento, size);
			corrimiento += size;
			fin = 0;
		}
		fat32_mandarAEscribirCluster(ClusterAEscribir, posicionFAT);
		sem_wait(&fatSemaforo);
		posicionFAT = fat32_SiguienteCadenaFat(posicionFAT);
		sem_post(&fatSemaforo);
		offsetDentroClus = 0;
		free(ClusterAEscribir);
	}
	return sizeCopiados;
}

int hello_create(const char *path, mode_t modo, struct fuse_file_info *fi){
	infoDir info;
	info = buscar(path);
	if (info.resultadoBusqueda == encontrado)
		return -ENOENT;
	char *pathDest;
	pathDest = directorioContenido(path);
	info = buscar(pathDest);

	if (info.resultadoBusqueda == noEncontrado)
		return -ENOENT;

	char *nombreArch;
	nombreArch = nombreDestino(path);

	uint32_t posLibre;
	Dir* Cluster = fat32_buscarPosLibreEnDirectorio(&posLibre, info);
	LDir LFN;
	Dir DIR;
	memset(&DIR, 0, sizeof(Dir));
	memset(&LFN, 0, sizeof(LDir));
	grabarNombreUTF16(&LFN, nombreArch);
	LFN.Ord = 0x41; //todo probar con 0x40
	LFN.Attr = 0x0F;
	LFN.Type = 0x00;
	LFN.FstClusLO = 0x0000;
	DIR.Attr = 0x20;
	DIR.FileSize = 0; //El fileSize va a ser 0 pero va a tener un Cluster si o si
	DIR.Name[0] = 0x64; // para q no este vacia esta entrada, por esto muestra d
	/*sem_wait(&fatSemaforo);

	t_queue* clusterLibre;
	clusterLibre = fat32_ObtenerNClustersLibres(1);
	nroCluster = (uint32_t) collection_queue_pop(clusterLibre);
	FAT.tabla[nroCluster - FAT.offset] = 0x0FFFFFFF;
	FAT.bitDeUso = modificado;
	sem_post(&fatSemaforo);*/
	uint32_t nroCluster = 0;
	char *src = (char *) &nroCluster;
	char *dst = (char *) &(DIR.FstClusLO);
	dst[0] = src[0];
	dst[1] = src[1];
	dst = (char *) &(DIR.FstClusHI);
	dst[0] = src[2];
	dst[1] = src[3];
	memcpy(&Cluster[posLibre], &LFN, 32);
	Cluster[posLibre + 1] = DIR;
	fat32_mandarAEscribirCluster((char*) Cluster, info.clusterContenido);
	free(Cluster);
	//collection_queue_destroy(clusterLibre, NULL);
	fat32_EscribirFatRegion();
	hello_open(path, NULL);
	return 0;
}

int hello_release(const char *path, struct fuse_file_info *fi){
	if(liberarCache(path)==1){
		return -ENOENT;
	}
	return 0;
}
